package viper.badRelays;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Random;

import viper.main.ViperHeader;

/**
 * @author Kevin Sin
 * 
 * Revision number: 3
 * Date created: 3/21/10
 * Date of Last Back-up on SVN: 3/21/10
 * 
 * <p>This class is a low-level class that just parses
 * the packets received from UDPReceiver.  If it is a call 
 * receive packet, use CallReceiver.INSTANCE.parsePacket().
 */
public class BadRelaysUDPReceiverWorker implements Runnable {
	
	public DatagramPacket packet;
	private byte[] bytes;
	private byte[] temp;
	private boolean currentState; //high if in high-loss, low if in low-loss
	private Random rgen;
	
	private double dropRateH, dropRateL, transRateH, transRateL;
	private long delay, delayBottom;

	public BadRelaysUDPReceiverWorker(DatagramPacket packet)
	{
		this.packet = packet;
		rgen = new Random();
		currentState = false;
		delayBottom = 0;
	}
	
	@Override
	public void run() {
		bytes = packet.getData();
		
		switch(bytes[0]) {
			case ViperHeader.RELAY:
				handleRelayPacket();
				break;
			default: 
				System.out.println("ignore"); 
				break;
		} 
	}

	/**
	 * At a relay, upon receiving a packet from the Sender, 
	 * the relay changes the VIPER header
	 * of the packet and forwards it to the sender.
	 * 
	 * Format of a relay packet
	 * <Relay (1 byte)> <RX IP Addr (4 bytes)> <Seq # (1 byte)> <Pkt Type (1 byte)> <Data>
	 */
	private void handleRelayPacket() {
		System.out.println("Handle Relay Pkt");
		temp = new byte[bytes.length - 4]; //temporary byte[] to store the payload
		temp[0] = ViperHeader.RECEIVE;
		System.arraycopy(bytes, 5, temp, 1, bytes.length - 5);
		packet.setData(temp);
		byte[] ipByte = new byte[4];
		System.arraycopy(bytes, 1, ipByte, 0, 4);
		
		delay = BadRelaysStateManager.INSTANCE.getDelay();
		dropRateH = BadRelaysStateManager.INSTANCE.getDrop_Rate_Hi(); //set by GUI
		dropRateL = BadRelaysStateManager.INSTANCE.getDrop_Rate_Lo(); //hard-coded
		transRateH = BadRelaysStateManager.INSTANCE.getTransition_Rate_toHi(); //set by GUI
		transRateL = BadRelaysStateManager.INSTANCE.getTransition_Rate_toLo(); //hard-coded
		
		//TODO: increment delayedPacketCount and droppedPacketCount in BadRealysStateManager.
		int droppedPacketCount = BadRelaysStateManager.INSTANCE.getDroppedPacketCount();
		int delayedPacketCount = BadRelaysStateManager.INSTANCE.getDelayedPacketCount();
		ViperBadRelaysGUI.INSTANCE.updateRelaysInternalFrame(droppedPacketCount, delayedPacketCount);
		
		InetAddress rxAddr = null;
		try {
			rxAddr = InetAddress.getByAddress(ipByte);
			packet.setAddress(rxAddr);
			
			// network delay/drop simulation
			checkTransition(transRateH, transRateL);
			if(checkDrop()){
				uniformDelay(delay);
				BadRelaysStateManager.INSTANCE.getDatagramSocket().send(packet);
			}
			
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}catch (SocketException e2) {
			e2.printStackTrace();
		} catch (IOException e3) {
			e3.printStackTrace();
		}
	}
	
	/**
	 * Handles transitions between high-/low-loss states and sets appropriate drop state of network loss
	 * @param lo
	 * @param loDrop
	 * @param hi
	 * @param hiDrop
	 */
	private void checkTransition(double toHi, double toLo){
		double roll = rgen.nextDouble();
		if(!currentState){ //if in low-loss state
			if(roll<=toHi){ //there was a transition to high-loss state
				currentState = true;
				System.out.println("High-Loss");
			}
		}
		else{ //if in high-loss state
			if(roll<=toLo){ //there was a transition to low-loss state 
				currentState = false;
				System.out.println("Low-Loss");
			}
		}
	}
	
	/**
	 * @param rate
	 * @return Returns false if packet isn't to be dropped, 
	 * true if it should be droppped.
	 */
	private boolean checkDrop(){
		double roll = rgen.nextDouble();
		//System.out.println("::DEBUG, Packet Loss Rolled: " + roll);
		if(!currentState){ //if in low-loss state
			if(roll>dropRateL) //use low-loss drop rate
				return true;
			else
				return false;
		}
		else{ //if in high-loss state
			if(roll>dropRateH) //use high-loss drop rate
				return true;
			else
				return false;
		}
	}
	
	/**
	 * Delays/sleeps the current packet/relay by a random delay from a uniform distribution between low and high [ms]
	 * @param low
	 * @param high
	 */
	private void randomDelay(long low, long high){
		long delay = rgen.nextInt((int)high-(int)low)+(int)low;
		
		if(delay!=0){
			try{
				Thread.currentThread().sleep(delay);
				System.out.println("::DEBUG, Random Delay: "+delay+"ms");
			}catch(InterruptedException ie){
				System.out.println("Interrupted Exception during random delay.");
			}
		}
	}
	
	private void uniformDelay(long delay){
		try{
			Thread.currentThread().sleep(delay);
			System.out.println("::DEBUG, Random Delay: "+delay+"ms");
		}catch(InterruptedException ie){
			System.out.println("Interrupted Exception during random delay.");
		}
	}
}
